﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ShootMan
{
    public class NetServiceComponent : MonoBehaviour
    {
        private class SendDataInfo : shaco.Base.IObjectPoolData
        {
            public NetEvent.HeaderData header = new NetEvent.HeaderData();
            public shaco.Base.DataBuffer data = new shaco.Base.DataBuffer();
            public System.Action<NetEvent.INetEvent> callbackReceive = null;

            public void OnRecycle()
            {
                header.uuid = null;
                header.dataLength = 0;
                header.messageID = GameDefines.NetMesssageID.None;
                data.Clear();
                callbackReceive = null;
            }
        }

        [SerializeField]
        private bool _isOpen = true;

        [SerializeField]
        private string _ipListen = "127.0.0.1";

        [SerializeField]
        private int _portListen = 6666;

        static public int playerID { get { return null == _instance ? -1 : _instance._playerID; } }
        [SerializeField]
        [shaco.ReadOnly]
        private int _playerID = -1;

        private System.Action<NetEvent.INetEvent> _callbackReceiveData = null;
        private shaco.Base.DataBuffer _netDataBuffer = new shaco.Base.DataBuffer();
        private shaco.Base.DataBuffer _netReceiveBuffer = new shaco.Base.DataBuffer();
        private Dictionary<string, SendDataInfo> _sendDatas = new Dictionary<string, SendDataInfo>();

        static private NetServiceComponent _instance = null;

        void Awake()
        {
            _instance = this;

            if (!_isOpen)
                return;

            var defaultHeader = new NetEvent.HeaderData();
            shaco.GameHelper.netClient.SetHeaderParse(defaultHeader.headerLength, (bytes) =>
            {
                lock (_netReceiveBuffer)
                {
                    _netReceiveBuffer.ReWriteBytes(bytes);
                    return NetEvent.NetHelper.ReadHeader(_netReceiveBuffer);
                }
            });

            shaco.GameHelper.netClient.AddDisconnectListener((sender) =>
            {
                shaco.Log.Info("NetServiceComponent sockect is disconnected");
            });

            shaco.GameHelper.netClient.AddReconnectListener((sender, callback) =>
            {
                shaco.Log.Info("NetServiceComponent reconnect: login start");
                NetServiceComponent.SendMessage(new NetEvent.C2S_Login() { }, (data) =>
                {
                    var loginData = data as NetEvent.S2C_Login;
                    shaco.Log.Info("NetServiceComponent reconnect: login success playerID=" + loginData.playerID);

                    //重连成功后继续发送直接还未发送完毕的事件
                    foreach (var iter in _sendDatas)
                    {
                        shaco.GameHelper.netClient.Send(iter.Value.data.ToBytes());
                        iter.Value.RecyclingWithPool();
                    }
                    _sendDatas.Clear();
                });
            });
        }

        void OnDestroy()
        {
            if (!_isOpen)
                return;

            if (shaco.GameHelper.netClient.IsConnected())
            {
                shaco.GameHelper.netClient.Disconnect();
            }
        }

        static public void Connect(System.Action<bool> callbackEnd)
        {
            CheckDefaultPackageValid();

            if (!_instance._isOpen)
            {
                callbackEnd(true);
                return;
            }

            if (null == _instance || shaco.GameHelper.netClient.IsConnected())
            {
                callbackEnd(false);
                return;
            }

            //开始连接服务器
            shaco.Log.Info("NetServiceComponent connect: listen=" + _instance._ipListen + " port=" + _instance._portListen);
            shaco.GameHelper.netClient.Connect(_instance._ipListen, _instance._portListen, (success) =>
            {
                shaco.Log.Info("NetServiceComponent connect: login start, success=" + success, Color.white);

                //开始监听数据回调
                if (success)
                {
                    shaco.GameHelper.netClient.AddReceiveListener(OnDataReceivedCallBack);

                    NetServiceComponent.SendMessage(new NetEvent.C2S_Login() { }, (data) =>
                    {
                        var loginData = data as NetEvent.S2C_Login;
                        shaco.Log.Info("NetServiceComponent connect: login success id=" + loginData.playerID, Color.white);
                        UserData.playerID = loginData.playerID;
                    });
                }
                callbackEnd(success);
            });
        }

        static public void SendMessage(NetEvent.C2S_INetEvent sendEvent, System.Action<NetEvent.INetEvent> callbackReceive = null)
        {
            CheckDefaultPackageValid();
            if (!_instance._isOpen)
            {
                return;
            }

            if (null == _instance || !shaco.GameHelper.netClient.IsConnected())
            {
                shaco.Log.Error("NetServiceComponent SendMessage error: not connected");
                return;
            }

            var sendDataInfo = shaco.GameHelper.objectpool.Instantiate(() => new SendDataInfo());
            sendDataInfo.header.uuid = NetEvent.HeaderData.NewUUID();
            sendDataInfo.header.messageID = sendEvent.messageID;
            sendDataInfo.callbackReceive = callbackReceive;
            NetEvent.NetHelper.WriteHeaderAndBody(sendDataInfo.data, sendDataInfo.header, sendEvent);

            shaco.GameHelper.netClient.Send(sendDataInfo.data.ToBytes());
            _instance._sendDatas.Add(sendDataInfo.header.uuid, sendDataInfo);
        }

        static public void AddDataReceiveListener(System.Action<NetEvent.INetEvent> callback)
        {
            CheckDefaultPackageValid();
            _instance._callbackReceiveData += callback;
        }

        static public void RemoveDataReceiveListener(System.Action<NetEvent.INetEvent> callback)
        {
            CheckDefaultPackageValid();
            _instance._callbackReceiveData -= callback;
        }

        static public void ClearDataReceiveListener()
        {
            CheckDefaultPackageValid();
            _instance._callbackReceiveData = null;
        }

        static private void OnDataReceivedCallBack(object sender, shaco.Base.Net.ReceiveEventInfo info)
        {
            CheckDefaultPackageValid();
            var headerInfo = (NetEvent.HeaderData)info.header;
            lock (_instance._netReceiveBuffer)
            {
                //解析包体内容并回调
                if (null != _instance._callbackReceiveData)
                {
                    var messageID = headerInfo.messageID;
                    var receiveEvent = shaco.Base.Utility.Instantiate(NetEvent.NetHelper.GetS2CEventTypeName(messageID)) as NetEvent.INetEvent;
                    if (null != receiveEvent)
                    {
                        _instance._netReceiveBuffer.ReWriteBytes(info.body);
                        _instance._netReceiveBuffer.Seek(0, System.IO.SeekOrigin.Begin);
                        receiveEvent.ReadBody(_instance._netReceiveBuffer);
                        _instance._callbackReceiveData(receiveEvent);

                        CheckRemoveSendData(headerInfo.uuid, receiveEvent);
                    }
                    else
                    {
                        CheckRemoveSendData(headerInfo.uuid, null);
                        shaco.Log.Error("NetServiceComponent OnDataReceivedCallBack error: invalid message id=" + messageID);
                    }
                }
                else
                {
                    shaco.Log.Warning("NetServiceComponent OnDataReceivedCallBack warning: no receive data callback, message id=" + headerInfo.messageID);
                    CheckRemoveSendData(headerInfo.uuid, null);
                }
            }
        }

        static private SendDataInfo CheckRemoveSendData(string uuid, NetEvent.INetEvent receiveEvent)
        {
            CheckDefaultPackageValid();
            
            SendDataInfo retValue = null;
            if (_instance._sendDatas.TryGetValue(uuid, out retValue))
            {
                if (null != retValue && null != retValue.callbackReceive)
                {
                    retValue.callbackReceive(receiveEvent);
                }

                retValue.RecyclingWithPool();
                _instance._sendDatas.Remove(uuid);
            }
            return retValue;
        }

        [System.Diagnostics.Conditional("DEBUG")]
        static void CheckDefaultPackageValid()
        {
            if (null == _instance)
                throw new System.Exception("NetServiceComponent CheckDefaultPackageValid error: not init on 'Awake'");
        }
    }
}